﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
//using System.Linq;
using System.Text;

using System.Net;                       // Lib net
using System.Net.NetworkInformation;
using System.Net.Sockets;               // Socket
using System.Threading;                 // Thread
using System.IO;                        // Pour la gestion entree/sortie
using System.Security.Cryptography;     // Pour MD5

using System.Web;

namespace netsoul
{
    public enum nsStatus
    {
        present = 0, 
        away = 1,
        no_change = 2,
        idle = 3,
        locked = 4,
        server = 5,
        none
    }

    public enum typeMsgServer
    {
        tms_login, 
        tms_logout,
        tms_userState
    }

    public enum stateUser
    {
        su_actif,
        su_unactif
    }

    public class ClientUser
    {
        #region Variables
        
        private string Flogin;
        private List<UInt32> Fsocket;
        private List<string> Fip;
        private List<int> Flevel_low;
        private List<int> Flevel_high;
        private List<string> Flocation;
        private List<nsStatus> Fstatut;
        private string Fgroup;
        private string Fcmd;
        private int Fnb_connection = 0;

        #endregion Variables

        #region Constructeur

        internal ClientUser() { }

        /// <summary>
        /// Creer un utilisateur.
        /// </summary>
        /// <param name="msg">String envoyé par le serveur.</param>
        internal ClientUser(string msg)
        {
            string []tab = msg.Split( ':' );
            this.Fsocket.Add(Convert.ToUInt32(tab[0]));
            this.Flogin = tab[1];

        }

        internal ClientUser(string user,UInt32 socket, string ip, int level_low, int level_high, string location, nsStatus statut, string group, string cmd)
        {
            Flogin = user;
            Fsocket.Add(socket);
            Fip.Add(ip);
            Flevel_low.Add(level_low);
            Flevel_high.Add(level_high);
            Flocation.Add(location);
            Fstatut.Add(statut);
            Fgroup = group;
            Fcmd = cmd;
            Fnb_connection = 1;
        }
        #endregion

        #region Methodes 

        public void DeleteRec(UInt32 socket)
        {
            if (Fnb_connection > 0)
                for (int i = 0; i < Fsocket.Count; i++)
                {
                    if (Fsocket[i].Equals(socket))
                    {
                        Fsocket.RemoveAt(i);
                        Fip.RemoveAt(i);
                        Flevel_high.RemoveAt(i);
                        Flevel_low.RemoveAt(i);
                        Flocation.RemoveAt(i);
                        Fstatut.RemoveAt(i);
                        Fnb_connection--;
                    }
                }
        }

        public void UpdateUser(UInt32 socket, string ip, int level_low, int level_high, string location, nsStatus statut, string cmd)
        {
            for (int i = 0; i < Fip.Count; i++)
            {
                if (Fip[i].Equals(ip))
                {
                    Fcmd = cmd;
                    Fstatut[i] = statut;
                    return;
                }
            }
            Fsocket.Add(socket);
            Fip.Add(ip);
            Flevel_high.Add(level_high);
            Flevel_low.Add(level_low);
            Flocation.Add(location);
            Fstatut.Add(statut);
            Fcmd = cmd;
            Fnb_connection++;
            return;
        }

        public override string ToString()
        {
            return Flogin;
        }

        #endregion Methodes

        #region Var

        public string login
        {
            get
            {
                return this.Flogin;
            }
        }

        #endregion

    }

    public class ClientUserList : ReadOnlyCollection<ClientUser>
    {
        #region Variables
        
        private int Fnb_online;
        
        #endregion Variables

        #region Constructeur

        public ClientUserList(IList<ClientUser> list)
            : base(list)
        {

        }

        #endregion Constructeur

        #region Methodes


        /// <summary>
        /// Permet d'ajouter un user dans la liste des utilisateurs.
        /// </summary>
        /// <param name="msg">Message preformaté retourné par le serveur</param>
        /// <returns></returns>
        public bool add_user(string msg)
        {
            string[] tab = msg.Split( ':' );
            return false;
        }

        #endregion Methodes
    }


    public delegate void NetsoulStateEventArgsHandler(object sender, NetsoulStateEventArgs e);

    public class NetsoulStateEventArgs : EventArgs
    {
        private typeMsgServer _type;
        private string _message;
        private ClientUser _user;

        public NetsoulStateEventArgs ( string msg_serv )
        {
            
        }

        public ClientUser User
        {
            get
            {
                return _user;
            }
        }

        public typeMsgServer MsgType
        {
            get
            {
                return _type;
            }
        }
    }

    public class Netsoul : netsoul.INetsoul
    {

        #region Declaration Des Variables

        private static string MSG_FORMAT_SEND = "msg_user {0} msg {1} dest=*:{0}@*{2}*";

        private nsStatus state = nsStatus.present;
        private string msg;
        private string login;
        private string pass;
        private string serv;
        private string data;
        private string location;
        private int port;
        public Socket Connection = null;
        private TcpClient sock;
        private NetworkStream sock_stream;
        private System.IO.StreamWriter envoie;
        private System.IO.StreamReader lire;
        private DateTime dtTimestamp;
        private bool online;
        private Thread tListen;

        private ArrayList FListUsers;

        #endregion Declaration Des Variables

        #region Constructeurs

        public Netsoul()
        {
            state = nsStatus.present;
            online = false;
            login = string.Empty;
            pass = string.Empty;
            serv = string.Empty;
            port = 0;
            msg = "Nothing";
            dtTimestamp = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime();
        }

        public Netsoul(string Login)
        {
            state = nsStatus.present;
            online = false;
            login = Login;
            pass = string.Empty;
            serv = string.Empty;
            port = 0;
            msg = "Nothing";
            dtTimestamp = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime();
        }

        public Netsoul(string Login, string Pass)
        {
            state = nsStatus.present;
            online = false;
            login = Login;
            pass = Pass;
            serv = string.Empty;
            port = 0;
            msg = "Nothing";
            dtTimestamp = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime();
        }

        public Netsoul(string Login, string Pass, string Serv, int Port)
        {
            state = nsStatus.present;
            online = false;
            login = Login;
            pass = Pass;
            serv = Serv;
            port = Port;
            msg = "Nothing";
            dtTimestamp = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime();
        }

        #endregion Constructeurs

        #region Destructeur
        ~Netsoul()
        {

        }
        #endregion Desctructeur

        #region ListeUser
        public void AddUser(string identifiant)
        {
            string cmd = "user_cmd watch_log_user " + identifiant.Trim();
            this.envoie.WriteLine(cmd);
            this.envoie.Flush();
            cmd = this.GetCmd();
            string[] cmdtab = cmd.Split('\n');
            for (int i = 0; i < cmdtab.Length; i++)
            {
                if (cmdtab[i].Trim().StartsWith("user_cmd") == false)
                    return;
                if (cmdtab.Length < 3)
                    return;
                string[] info = cmdtab[1].Trim().Split(':');
                if (info.Length < 7)
                    return;
                ClientUser user = new ClientUser(identifiant.Trim().ToString());
                int socket = int.Parse(info[0].Trim());
            }
        }

        #endregion ListeUser

        #region Event

        public delegate void dlgUserNotification ( ClientUser user, typeMsgServer type );
        public event dlgUserNotification NewUserNotification;

        public delegate void dlgNewUserMessage ( ClientUser user, string message );
        public event dlgNewUserMessage NewUserMessage;

        public delegate void dlgDebugMessage ( string message );
        public event dlgDebugMessage DebugMessage;

        #endregion

        #region Methodes

        static private string MD5String(string s)
        {
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] input = System.Text.Encoding.ASCII.GetBytes(s);
            byte[] hash = md5.ComputeHash(input);

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("x2"));
            }
            return sb.ToString();
        }

        static private string Create_msg(string[] tab, Netsoul user)
        {
            string msg_MD5 = MD5String(tab[2] + "-" + tab[3] + "/" + tab[4] + user.pass.Trim());
            string msg = "ext_user_log"
                + " "
                + user.login.Trim()
                + " "
                + msg_MD5
                + " "
                + System.Web.HttpUtility.UrlEncode(user.location).Replace("+", "%20")
                + " "
                + System.Web.HttpUtility.UrlEncode(user.data).Replace("+", "%20");
            return msg;
        }

        public static string EncodeMSG(string msg)
        {
            string nmsg = string.Empty;
            nmsg = System.Web.HttpUtility.UrlEncode(msg).Replace("+", "%20");
            return (nmsg);
        }

        public static string DecodeMSG(string msg)
        {
            string nmsg = string.Empty;
            nmsg = System.Web.HttpUtility.UrlDecode(msg.Replace("%20", "+")).Replace("\\n", "\r\n");
            return (nmsg);
        }

        private double DateToTimestamp(DateTime date)
        {
            TimeSpan temps = (date - this.dtTimestamp);
            return (double)temps.TotalSeconds;
        }

        private DateTime TimeToDate(double timestamp)
        {
            System.DateTime date = this.dtTimestamp;
            date.AddSeconds(timestamp);
            return (date);
        }

        private bool Valid_cmd(string msg)
        {
            string rep = this.GetCmd();
            if (rep == null || rep.Length <= 0 || rep.Contains(msg) == false)
            {
                this.msg = msg + " | server send : " + rep;
                return false;
            }
            return true;
        }

        public string GetCmd()
        {
            if (this.sock.Connected == false)
            {
                this.msg = "Error : Not connected.";
                return string.Empty;
            }
            string msg = string.Empty;
            while (this.sock_stream.DataAvailable == true)
            {
                msg += this.lire.ReadLine();
            }
            return msg;
        }

        public bool SendCmd(string cmd, string rep)
        {
            if (this.sock.Connected == false)
            {
                this.msg = "Error : can't send a cmd when the sock is not connected";
                return false;
            }
            this.envoie.WriteLine(cmd);
            this.envoie.Flush();
            Thread.Sleep(300);
            if (Valid_cmd(rep) == false)
                return false;
            return true;
        }

        public void SendCmd(string cmd)
        {
            this.envoie.WriteLine(cmd);
            this.envoie.Flush();
        }

        public void Change_status(nsStatus Status)
        {
            this.state = Status;
            Change_status();
        }

        private void Change_status()
        {
            string s = string.Empty;
            switch (this.state)
            {
                case nsStatus.present:
                    s = "actif";
                    break;
                case nsStatus.away:
                    s = "away";
                    break;
                case nsStatus.idle:
                    s = "idle";
                    break;
                case nsStatus.no_change:
                    s = "connection";
                    break;
                case nsStatus.locked:
                    s = "lock";
                    break;
                case nsStatus.server:
                    s = "server";
                    break;
                case nsStatus.none:
                default:
                    s = "none";
                    break;
            }
            this.SendCmd("user_cmd state " + s.ToString() + ":" + this.GetTimestampNow().ToString());
        }

        public double GetTimestampNow()
        {
            TimeSpan time = DateTime.Now - this.dtTimestamp;
            return (double)time.TotalSeconds;
        }

        public bool Register()
        {
            if (this.login.Trim().Length <= 0)
            {
                this.msg = "Error: Need Login";
                return (false);
            }
            if (this.pass.Length <= 0)
            {
                this.msg = "Error: Need Password";
                return (false);
            }
            if (this.serv.Trim().Length <= 0)
            {
                this.msg = "Error: Need Host";
                return (false);
            }
            if (this.port < 0 || this.port > 65555)
                this.port = 4242;
            try
            {
               this.sock = new TcpClient( Properties.Settings.Default.server, this.port );
            }
            catch
            {
                this.msg = "Error: TcpClient";
                return false;
            }

            this.sock_stream = this.sock.GetStream();
            this.lire = new System.IO.StreamReader(sock_stream);
            this.envoie = new System.IO.StreamWriter(sock_stream);

            // On recupere la 1ere ligne pour la connection
            string rep = this.lire.ReadLine();
            string[] tab = rep.Trim().Split(' ');
            if (tab.Length != 6)
                return false;
            if (this.SendCmd("auth_ag ext_user none none", "rep 002") == false)
                return false;
            Thread.Sleep(300);
            rep = Create_msg(tab, this);
            if (this.SendCmd(rep, "rep 002") == false)
                return false;
            this.online = this.sock.Connected;
            this.SendCmd("user_cmd attach");
            return true;
        }

        public void Send_message_login(string login, string message)
        {
            string cmd = "user_cmd msg_user " + login + " msg " + System.Web.HttpUtility.UrlEncode(message, Encoding.ASCII).Replace("+", "%20");
            this.SendCmd(cmd);
        }

        public void Send_message_login ( string login, string message, string location )
        {
           string cmd = "user_cmd " + string.Format( MSG_FORMAT_SEND, login, System.Web.HttpUtility.UrlEncode( message, Encoding.ASCII ).Replace( "+", "%20" ), location );
           this.SendCmd( cmd );
        }

        public void Deconnection()
        {
            this.sock.Close();
            this.online = this.sock.Connected;
        }

        public string ReadLineSocket()
        {
            string read = string.Empty;

            read = this.lire.ReadLine();
            return read;
        }

        public void ListenServer ()
        {
            if ( this.tListen == null )
            {
                this.tListen = new Thread( new ThreadStart( _ListenServer ) );
                this.tListen.Start();
            }
        }

        /// <summary>
        /// Methode qui lance un thread en background et qui traite tout. Methode asynchrone
        /// </summary>
        private void _ListenServer ()
        {
            string _read;
            string[] _read_tab;

            // Si on est connecter alors on ecoute
            if ( this.online )
            {
                while ( this.online )
                {
                    _read = this.ReadLineSocket();
                    if ( this.DebugMessage != null )
                        this.DebugMessage( Netsoul.DecodeMSG( _read ) );

                    // Si on a un ping envoyer par le server alors on rep tout de suite
                    if ( _read.StartsWith( "ping" ) == true )
                    {
                        this.SendCmd( _read );
                    }

                    // On check si on chope une info d'un user
                    if (_read.StartsWith("user_cmd") == true)
                    {
                        _read_tab = _read.Split( '|' );
                        if ( _read_tab.Length > 1 && _read_tab[1].StartsWith( "login" ) )
                        {
                            if ( this.NewUserNotification != null )
                            {

                            }
                        }
                    }
                    Thread.Sleep( 10 );
                }
            }
        }

        #endregion Methodes

        #region Proprietes

        public bool Connected
        {
            get
            {
                return online;
            }
        }

        public string Login
        {
            get 
            {
                return login;
            }
            set
            {
                login = value;
            }
        }

        public string MotDePasse
        {
            set
            {
                pass = value;
            }
        }

        public string Serveur
        {
            get
            {
                return serv;
            }
            set
            {
                serv = value;
            }
        }

        public int Port
        {
            get
            {
                return port;
            }
            set
            {
                port = value;
            }
        }

        public string message_log
        {
            get
            {
                return msg;
            }
        }

        public string Location
        {
            get
            {
                return this.location;
            }
            set
            {
                this.location = value;
            }
        }

        public string Data
        {
            get
            {
                return this.data;
            }
            set
            {
                this.data = value;
            }
        }

        public nsStatus Statut
        {
            get
            {
                return this.state;
            }
            set
            {
                Change_status(value);
            }
        }

        #endregion Proprietes
    }
}
